-
Notifications
You must be signed in to change notification settings - Fork 146
fix(ui): dashboard UX improvements and i18n fixes #650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Remove bold font from model name in usage logs table - Rename "Quota Usage" to "Quota User Usage" - Rename "Statistics Summary" to "Key Statistics" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…board charts - Use useLocale() from next-intl to get current locale - Replace hardcoded "zh-CN" in toLocaleTimeString/toLocaleDateString/toLocaleString - Move chartConfig inside component to use translated label - Add locale to useMemo dependencies where needed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add connectionUnavailable key to all 5 language files - Use translated message in database-status.tsx when isAvailable is false - Handle both HTTP 503 errors and status.error from API response Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously, the provider model test ignored the configured proxy URL and always made direct connections. Now the test respects proxyUrl setting by creating a dispatcher via createProxyAgentForProvider. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…tion - Add providersBatchEdit import to all 5 locale index.ts files - Update provider group field description to mention select/create behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix MCP passthrough select to show translated label instead of value - Fix raw response overflow with break-all and overflow-x-hidden Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Shrink Time column (flex 0.8→0.6, min-width 80→56px) and expand User column (flex 0.6→0.8) to show more of the username. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Apply color coding to cache hit rate column: green (>=85%), yellow (60-84%), orange (<60%) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add token statistics (input, output, cache creation, cache read) to SQL queries - Redesign key stats dialog with summary cards and compact model rows - Add cache hit rate indicator per model with color coding - Fix user limit refresh by clearing usage cache on refresh button click - Add i18n translations for new modal fields (5 languages) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add header and bullet list styling to group tooltips for consistency with request-filters tooltip design. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Show compact badge with clock icon and days remaining when <= 7 days - Display tooltip with full localized text on hover - Rename column header from "Edit" to "Status" in user mode - Add i18n support for all 5 languages (en, zh-CN, zh-TW, ja, ru) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Wrap chartConfig in useMemo to prevent recreation on every render - Wrap daysLeft calculation in useMemo with localExpiresAt dependency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthrough该 PR 为多语言添加代币与缓存相关的翻译键,扩展并返回密钥统计的代币分解字段,重构密钥统计对话框为卡片式布局,引入区域感知的日期/时间格式化,增加数据库连接不可用错误处理,并在提供商测试中添加可选代理支持与相关缓存与 UI 改动。 Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @miraserver, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly refines the dashboard's user experience and strengthens its internationalization capabilities. It introduces new features that provide users with more granular insights into their key usage and model performance, while also addressing several UI inconsistencies and improving the overall responsiveness of the application. The changes ensure a more intuitive and globally friendly interface. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review Summary
This PR adds UI/UX improvements and i18n fixes to the dashboard. The changes are well-structured with proper i18n support across all 5 languages. However, there is one critical issue that will cause build failure.
PR Size: M
- Lines changed: 601 (483 additions + 118 deletions)
- Files changed: 49
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 1 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Critical Issues (Must Fix)
1. Missing batchEdit.json translation files (Build Failure)
- Location:
messages/*/settings/index.ts(lines 93, 210, 327, 447, 566) - Issue: All 5 language index.ts files import
providersBatchEdit from "./providers/batchEdit.json"but these files do not exist - Impact: This will cause TypeScript compilation errors and runtime import failures
- Files affected:
messages/en/settings/index.ts:93messages/ja/settings/index.ts:210messages/ru/settings/index.ts:327messages/zh-CN/settings/index.ts:447messages/zh-TW/settings/index.ts:566
- Fix: Either create the missing
batchEdit.jsonfiles inmessages/{locale}/settings/providers/for all 5 languages, OR remove the import/export statements if not needed yet
Review Coverage
- Logic and correctness - 1 critical issue found
- Security (OWASP Top 10) - Clean
- Error handling - Clean (proper error handling in database-status and test-service)
- Type safety - Clean (proper TypeScript types added for token stats)
- Documentation accuracy - Clean
- Test coverage - No new tests required (UI changes only)
- Code clarity - Good (proper memoization, clear variable names)
Positive Observations
- Proper i18n implementation across all 5 languages
- Good use of React.useMemo for performance optimization
- Proper TypeScript type definitions for new fields
- Clean error handling with translated messages
- Proper proxy support added to test-service
- Cache management with clearUsageCache function
Automated review by Claude AI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request includes several changes to improve the dashboard and settings pages. It adds a 'status' field to common translations, updates dashboard translations to include tokens and cache-related information, and modifies myUsage translations to be more specific about quota usage. It also adds a new 'batchEdit' feature to provider settings, updates provider group descriptions, and fixes a locale issue in date formatting. Additionally, it enhances the user interface with token statistics, cache hit rate indicators, and improved error handling for database connections. Review comments suggest adding JSDoc comments to the formatTokenAmount function, improving the title attribute for edit/status actions, using constants or utility functions for color classes, adding comments to explain the use of COALESCE in SQL queries, and extracting tooltip content into a separate component.
| const formatDate = (dateStr: string) => { | ||
| const date = new Date(dateStr); | ||
| if (data.resolution === "hour") { | ||
| return date.toLocaleTimeString("zh-CN", { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const formatTooltipDate = (dateStr: string) => { | ||
| const date = new Date(dateStr); | ||
| if (data.resolution === "hour") { | ||
| return date.toLocaleString("zh-CN", { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| function formatTokenAmount(tokens: number): string { | ||
| if (tokens >= 1_000_000_000) { | ||
| return `${(tokens / 1_000_000_000).toFixed(1)}B`; | ||
| } | ||
| if (tokens >= 1_000_000) { | ||
| return `${(tokens / 1_000_000).toFixed(1)}M`; | ||
| } | ||
| if (tokens >= 1_000) { | ||
| return `${(tokens / 1_000).toFixed(1)}K`; | ||
| } | ||
| return tokens.toLocaleString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| <div className="px-2 text-center min-w-0"> | ||
| <span className="block truncate" title={translations.actions.edit}> | ||
| {translations.actions.edit} | ||
| <span | ||
| className="block truncate" | ||
| title={isAdmin ? translations.actions.edit : translations.actions.status} | ||
| > | ||
| {isAdmin ? translations.actions.edit : translations.actions.status} | ||
| </span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To enhance user experience, consider using a more descriptive title attribute that includes the user's name or ID. This would provide more context when hovering over the edit/status action.
title={isAdmin ? `${translations.actions.edit} for ${user.name}` : `${translations.actions.status} for ${user.name}`}
>
| cell: (row) => { | ||
| const rate = Number((row as ProviderCacheHitRateEntry).cacheHitRate || 0) * 100; | ||
| const colorClass = | ||
| rate >= 85 | ||
| ? "text-green-600 dark:text-green-400" | ||
| : rate >= 60 | ||
| ? "text-yellow-600 dark:text-yellow-400" | ||
| : "text-orange-600 dark:text-orange-400"; | ||
| return <span className={colorClass}>{rate.toFixed(1)}%</span>; | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The color classes are hardcoded. Consider defining these color classes as constants or using a utility function to generate them based on the rate value. This would improve maintainability and readability.
const rate = Number((row as ProviderCacheHitRateEntry).cacheHitRate || 0) * 100;
const colorClass = rate >= 85 ? "text-green-600 dark:text-green-400" : rate >= 60 ? "text-yellow-600 dark:text-yellow-400" : "text-orange-600 dark:text-orange-400";
return <span className={colorClass}>{rate.toFixed(1)}%</span>;
| </div> | ||
| </TooltipTrigger> | ||
| <TooltipContent side="bottom" align="start"> | ||
| <ul className="text-xs space-y-1"> | ||
| {userGroups.map((group) => ( | ||
| <li key={group}>{group}</li> | ||
| ))} | ||
| </ul> | ||
| <div className="max-w-xs"> | ||
| <p className="font-medium mb-1">{translations.keyRow?.fields?.group}:</p> | ||
| <ul className="text-xs list-disc list-inside"> | ||
| {userGroups.map((group) => ( | ||
| <li key={group}>{group}</li> | ||
| ))} | ||
| </ul> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| clearUsageCache(); | ||
| refetch(); | ||
| }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a comment explaining why clearUsageCache is being called before refetch. This would improve code readability and make it easier for other developers to understand the purpose of this code.
onRefresh={() => {
// Clear the usage cache to ensure fresh data is fetched
clearUsageCache();
refetch();
}}
| if (response.status === 503) { | ||
| throw new Error(t("connectionUnavailable")); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a more specific error message for the 503 status code. This would provide more context to the user and make it easier to troubleshoot the issue.
| if (response.status === 503) { | |
| throw new Error(t("connectionUnavailable")); | |
| } | |
| // Use translated message for connection unavailable error | |
| if (response.status === 503) { | |
| throw new Error(t("connectionUnavailable")); | |
| } |
| </div> | ||
| </TooltipTrigger> | ||
| <TooltipContent side="bottom" align="start"> | ||
| <ul className="text-xs space-y-1"> | ||
| {userGroups.map((group) => ( | ||
| <li key={group}>{group}</li> | ||
| ))} | ||
| </ul> | ||
| <div className="max-w-xs"> | ||
| <p className="font-medium mb-1">{translations.keyRow?.fields?.group}:</p> | ||
| <ul className="text-xs list-disc list-inside"> | ||
| {userGroups.map((group) => ( | ||
| <li key={group}>{group}</li> | ||
| ))} | ||
| </ul> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if (response.status === 503) { | ||
| throw new Error(t("connectionUnavailable")); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 file reviewed, 1 comment
| const statTotalInput = | ||
| stat.inputTokens + stat.cacheCreationTokens + stat.cacheReadTokens; | ||
| const statCacheHitRate = | ||
| statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cache hit rate calculation includes cacheCreationTokens in the denominator, which may not be correct. Cache hit rate typically measures how often cached data is reused (cache reads) versus fresh data needed (regular input). Including cache creation tokens (which are cache writes) in the total input inflates the denominator.
Consider if the formula should be:
| const statTotalInput = | |
| stat.inputTokens + stat.cacheCreationTokens + stat.cacheReadTokens; | |
| const statCacheHitRate = | |
| statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0; | |
| const statTotalInput = stat.inputTokens + stat.cacheReadTokens; | |
| const statCacheHitRate = | |
| statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0; |
Or alternatively, if you want cache hit rate to be cache reads vs all cache operations:
const statTotalCache = stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate = statTotalCache > 0 ? (stat.cacheReadTokens / statTotalCache) * 100 : 0;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/_components/user/key-stats-dialog.tsx
Line: 187:190
Comment:
Cache hit rate calculation includes `cacheCreationTokens` in the denominator, which may not be correct. Cache hit rate typically measures how often cached data is reused (cache reads) versus fresh data needed (regular input). Including cache creation tokens (which are cache writes) in the total input inflates the denominator.
Consider if the formula should be:
```suggestion
const statTotalInput = stat.inputTokens + stat.cacheReadTokens;
const statCacheHitRate =
statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;
```
Or alternatively, if you want cache hit rate to be cache reads vs all cache operations:
```
const statTotalCache = stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate = statTotalCache > 0 ? (stat.cacheReadTokens / statTotalCache) * 100 : 0;
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@messages/en/myUsage.json`:
- Line 119: The title value "Quota User Usage" is awkward; update the "title"
field in messages/en/myUsage.json (the JSON object's "title" key currently set
to "Quota User Usage") to a clearer phrase such as "User Quota Usage" or "Quota
Usage" by replacing the string value accordingly and ensure surrounding JSON
syntax stays valid.
In `@src/app/`[locale]/dashboard/_components/user/user-key-table-row.tsx:
- Line 15: The countdown badge is shown as "today" when a key is already expired
because the remaining-seconds calculation returns 0; update the remaining-time
logic (e.g., getRemainingSeconds / remainingSeconds computed in the useMemo) to
yield a falsy/negative value or null when the key is expired, and change the
badge rendering (e.g., expiresBadge / renderExpiryBadge or the JSX that shows
the countdown) to only render the countdown when remainingSeconds > 0; ensure
the expired state (isExpired or the expiry icon rendering) still renders when
expired so the expired icon and badge do not conflict.
In `@src/app/`[locale]/settings/data/_components/database-status.tsx:
- Around line 25-31: Check response.status === 503 before calling
response.json() in the error handling branch inside the database-status
component: if status is 503, throw new Error(t("connectionUnavailable"))
immediately; otherwise safely attempt to parse response.json() (wrap in
try/catch) and then throw new Error(errorData?.error || t("error")). Reference
the existing symbols response, response.ok, response.status,
t("connectionUnavailable") and errorData when making the change.
In `@src/lib/provider-testing/test-service.ts`:
- Around line 81-96: The proxy agent creation using createProxyAgentForProvider
(which sets dispatcher and usedProxy) is executed outside the existing try/catch
and can throw, so move the entire proxy creation block into the same try that
builds the ProviderTestResult; catch any exception from
createProxyAgentForProvider and convert it into the structured
ProviderTestResult error path (same classification/fields used for other
failures), ensuring dispatcher is only set on success and usedProxy is updated
accordingly so failures return a proper ProviderTestResult instead of bubbling
an exception.
🧹 Nitpick comments (2)
src/app/[locale]/dashboard/_components/user/user-limit-badge.tsx (1)
79-113: 可选优化:并发请求去重当多个具有相同
userId的UserLimitBadge组件同时挂载时,可能会触发重复的 API 请求。可以考虑使用 pending promise 模式进行去重:♻️ 可选的去重优化方案
// 在文件顶部添加 pending requests 追踪 const pendingRequests = new Map<number, Promise<LimitUsageData | null>>(); // 在 useEffect 中使用 const pending = pendingRequests.get(userId); if (pending) { pending.then((data) => { if (data) setUsageData(data); else setError(true); }).finally(() => setIsLoading(false)); return; } const request = getUserAllLimitUsage(userId).then((res) => { // ... existing logic }); pendingRequests.set(userId, request); request.finally(() => pendingRequests.delete(userId));src/app/[locale]/dashboard/_components/statistics/chart.tsx (1)
332-332: 可选改进:数字格式化也传入 locale为保持一致性,
toLocaleString()用于数字格式化时也可以传入locale参数,尽管对于大多数常见语言差异较小。♻️ 可选的数字格式化统一方案
- {visibleTotals.calls.toLocaleString()} + {visibleTotals.calls.toLocaleString(locale)}类似地适用于第 358、431、486、588 行的
toLocaleString()调用。Also applies to: 358-358
- Move proxy creation into try/catch block (test-service.ts) - Check 503 status before parsing JSON (database-status.tsx) - Fix English grammar "User Quota Usage" (myUsage.json) - Return null for expired keys in getDaysLeft (user-key-table-row.tsx) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addressed PR Review FeedbackFixed the following issues from CodeRabbit review: Fixed Issues
Intentionally Not Changed
Commit: adb1d17 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/app/`[locale]/dashboard/_components/user/user-key-table-row.tsx:
- Around line 502-522: The daysLeft===0 branch is unreachable given getDaysLeft
returns null or >=1, and the daysLeft > 0 && daysLeft <= 7 check is redundant
because showExpiryBadge already guarantees that range; update the Badge
rendering: remove the daysLeft===0 ternary so variant is either "outline" or
another fixed variant (e.g., keep "outline"), and simplify the className
conditional to only check daysLeft <= 7 (or remove the check entirely and always
apply the small-range styling when showExpiryBadge is true). Change references
in the user-key-table-row rendering (Badge, daysLeft, showExpiryBadge)
accordingly; alternatively, if you intended a 0 state use getDaysLeft to return
0 near expiry instead of changing the UI here.
| ) : ( | ||
| showExpiryBadge && ( | ||
| <Tooltip> | ||
| <TooltipTrigger asChild> | ||
| <Badge | ||
| variant={daysLeft === 0 ? "destructive" : "outline"} | ||
| className={cn( | ||
| "text-xs cursor-help", | ||
| daysLeft > 0 && | ||
| daysLeft <= 7 && | ||
| "border-amber-500 text-amber-600 dark:text-amber-400" | ||
| )} | ||
| > | ||
| <Clock className="h-3 w-3 mr-1" /> | ||
| {daysLeft} | ||
| </Badge> | ||
| </TooltipTrigger> | ||
| <TooltipContent>{tUserStatus("daysLeft", { days: daysLeft })}</TooltipContent> | ||
| </Tooltip> | ||
| ) | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the user-key-table-row.tsx file to see the full context
wc -l src/app/[locale]/dashboard/_components/user/user-key-table-row.tsxRepository: ding113/claude-code-hub
Length of output: 137
🏁 Script executed:
# Look for getDaysLeft function in the file
rg "getDaysLeft" -A 15 --context=5 src/app/[locale]/dashboard/_components/user/user-key-table-row.tsxRepository: ding113/claude-code-hub
Length of output: 934
🏁 Script executed:
# Search for getDaysLeft in other files (it might be imported)
rg "export.*getDaysLeft|function getDaysLeft" -B 2 -A 15Repository: ding113/claude-code-hub
Length of output: 1961
🏁 Script executed:
# Find where tUserStatus is defined/imported
rg "tUserStatus|const tUserStatus|function tUserStatus" -B 3 -A 3 src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx | head -30Repository: ding113/claude-code-hub
Length of output: 1175
🏁 Script executed:
# Search for i18n translation files
find . -name "*.json" -path "*/messages/*" -o -name "*.ts" -path "*/i18n*" | grep -E "(messages|i18n)" | head -20Repository: ding113/claude-code-hub
Length of output: 757
🏁 Script executed:
# Look for dashboard.json or similar translation files
fd -e json -path "*/messages/*" | head -30Repository: ding113/claude-code-hub
Length of output: 235
🏁 Script executed:
# Find dashboard translation files
find ./messages -name "*dashboard*" -o -name "*userManagement*" -o -name "*userStatus*" | sortRepository: ding113/claude-code-hub
Length of output: 217
🏁 Script executed:
# List all json files in messages directories
ls -la ./messages/*/Repository: ding113/claude-code-hub
Length of output: 6775
🏁 Script executed:
# Look for daysLeft in all message files
rg "daysLeft" ./messages/Repository: ding113/claude-code-hub
Length of output: 661
消除无法触及的代码分支和冗余条件
根据 getDaysLeft 的实现(第 114-121 行),该函数返回 null 或 Math.ceil(正数) >= 1,永不返回 0。这导致第 507 行的 daysLeft === 0 条件永远为 false,"destructive" 变体无法被应用。
同时,showExpiryBadge 的定义已确保 daysLeft 在范围 [1, 7] 内,因此第 510-512 行的 daysLeft > 0 && daysLeft <= 7 在此上下文中恒为 true,属于冗余条件。
建议简化代码:
移除死代码和冗余条件
<Badge
- variant={daysLeft === 0 ? "destructive" : "outline"}
- className={cn(
- "text-xs cursor-help",
- daysLeft > 0 &&
- daysLeft <= 7 &&
- "border-amber-500 text-amber-600 dark:text-amber-400"
- )}
+ variant="outline"
+ className="text-xs cursor-help border-amber-500 text-amber-600 dark:text-amber-400"
>注:翻译文件中包含 =0 的复数形式(如 en 中的 "Expires today"),表明原始设计可能意图支持 daysLeft=0 的场景。若需在最后一天(不足 24 小时)显示 "destructive" 样式,应调整 getDaysLeft 逻辑以在接近过期时返回 0。
🤖 Prompt for AI Agents
In `@src/app/`[locale]/dashboard/_components/user/user-key-table-row.tsx around
lines 502 - 522, The daysLeft===0 branch is unreachable given getDaysLeft
returns null or >=1, and the daysLeft > 0 && daysLeft <= 7 check is redundant
because showExpiryBadge already guarantees that range; update the Badge
rendering: remove the daysLeft===0 ternary so variant is either "outline" or
another fixed variant (e.g., keep "outline"), and simplify the className
conditional to only check daysLeft <= 7 (or remove the check entirely and always
apply the small-range styling when showExpiryBadge is true). Change references
in the user-key-table-row rendering (Badge, daysLeft, showExpiryBadge)
accordingly; alternatively, if you intended a 0 state use getDaysLeft to return
0 near expiry instead of changing the UI here.
Re: Cache Hit Rate Formula (key-stats-dialog.tsx:187-190)The current formula is correct and aligns with Anthropic's official documentation. Current Implementationconst statTotalInput = stat.inputTokens + stat.cacheCreationTokens + stat.cacheReadTokens;
const statCacheHitRate = statTotalInput > 0 ? (stat.cacheReadTokens / statTotalInput) * 100 : 0;Why This Is Correct1. Anthropic's official documentation states:
Source: Prompt Caching - Tracking cache performance 2. Anthropic's official cookbook uses the same approach: total_input_tokens = input_tokens + cache_read_input_tokens
percentage_cached = cache_read_input_tokens / total_input_tokens * 100Source: Prompt Caching Cookbook Why the Suggested Alternatives Are Less Accurate
What Our Formula MeasuresCache Utilization Rate - the percentage of ALL processed input tokens that were served from cache. This is the most meaningful metric for understanding actual cost savings and efficiency. Conclusion: No changes needed. The current implementation correctly follows Anthropic's token accounting model. |
Summary
This PR contains 12 commits with UI/UX improvements and i18n fixes for the dashboard.
Related Issues:
Changes
Features
Fixes
Performance
Files Changed
Testing
bun run typecheck- passedbunx biome check- passed for modified filesChecklist
devGreptile Overview
Greptile Summary
This PR delivers comprehensive dashboard UX improvements and i18n fixes across 49 files, focusing on enhanced token statistics visibility, performance optimizations, and internationalization.
Key Enhancements:
i18n Fixes:
zh-CNlocale with dynamic locale in chart componentsUX Improvements:
The changes are well-structured with proper separation between backend data aggregation (repository layer) and frontend presentation (React components). All i18n strings are consistently added across all supported languages.
Confidence Score: 5/5
Important Files Changed
Sequence Diagram
sequenceDiagram participant User participant Dashboard participant KeyStatsDialog participant Repository participant DB User->>Dashboard: View user key statistics Dashboard->>Repository: findKeysWithStatistics(userId) Repository->>DB: Query message_request with token aggregations DB-->>Repository: Return model stats with tokens Note over DB,Repository: Includes inputTokens, outputTokens,<br/>cacheCreationTokens, cacheReadTokens Repository-->>Dashboard: UserKeyDisplay with modelStats[] User->>Dashboard: Click key stats button Dashboard->>KeyStatsDialog: Open dialog with modelStats KeyStatsDialog->>KeyStatsDialog: Calculate totals and cache hit rate Note over KeyStatsDialog: Cache hit rate = cacheReadTokens /<br/>(inputTokens + cacheCreationTokens + cacheReadTokens) KeyStatsDialog-->>User: Display enhanced modal Note over KeyStatsDialog,User: Shows cards with token breakdown,<br/>cache statistics, per-model details User->>Dashboard: View leaderboard Dashboard->>Dashboard: Render cache hit rates with colors Note over Dashboard: Green (≥85%), Yellow (≥60%),<br/>Orange (<60%) Dashboard-->>User: Display color-coded leaderboard